import os
import random
import struct
import serial
import sys
import time
import threading

OP_EXEC_CMD = 1
OP_SEND_FILE_HEADER = 2
OP_SEND_FILE_PACKET = 3
OP_SEND_FILE_END = 4

RESULT_EXEC_CMD = 0xF1
RESULT_FILE_HEADER = 0xF2
RESULT_FILE_END = 0xF3

SEND_FILE_PACKET_SIZE = 512 * 1024

device = None
shell = None
pending = False

class Job(object):
    def __init__(self, data, response=None, responseRequired=False):
        self.data = data
        self.response = response
        self.responseRequired = responseRequired


class FileSender(object):
    def __init__(self, path):
        super(FileSender, self).__init__()
        self.path = path

    def build_send_file_header(self):
        self.file_size = os.path.getsize(self.path)
        self.name = os.path.split(self.path)[1].encode('utf-8')
        self.file_name_size = len(self.name)
        self.uid = random.randint(0, 0xFFFF)
        data = struct.pack(
            '<HIH', self.uid, self.file_size, self.file_name_size)+self.name
        return struct.pack('<HI', OP_SEND_FILE_HEADER, len(data)) + data

    def build_send_file_packet(self, offset, data):
        data = struct.pack('<HII',
                           self.uid,
                           offset,
                           len(data))+data
        return struct.pack('<HI', OP_SEND_FILE_PACKET, len(data)) + data

    def build_send_file_end(self):
        return struct.pack('<HIH', OP_SEND_FILE_END, 2, self.uid)

    def send(self, shell):
        def onFileHeaderResponse(resultCode, data):
            print(data)

            fs = open(self.path, 'rb')
            offset = 0
            while(offset < self.file_size):
                data = self.build_send_file_packet(
                    offset, fs.read(SEND_FILE_PACKET_SIZE))
                offset = fs.tell()
                job = Job(data)
                shell.addToQueue(job)

            job = Job(self.build_send_file_end(),
                      response=onFileEndResponse, responseRequired=True)
            shell.addToQueue(job)

        def onFileEndResponse(resultCode, data):
            print data
            global pending
            pending = False

        job = Job(self.build_send_file_header(),
                  response=onFileHeaderResponse, responseRequired=True)
        shell.addToQueue(job)


class Shell(threading.Thread):
    def __init__(self, device):
        super(Shell, self).__init__()
        self.queue = []
        self.device = device
        self.stopRequested = False

    def run(self):
        while True:
            if len(self.queue) > 0:
                job = self.queue.pop()
                self.device.write(job.data)
                if job.responseRequired:
                    res = ''
                    while res != '\xAB':
                        sys.stdout.write(res)
                        res = self.device.read(1)
                    resultCode, dataSize = struct.unpack(
                        '<BI', self.device.read(5))
                    data = self.device.read(dataSize)
                    job.response(resultCode, data)
            else:
                sys.stdout.write(self.device.read(1))

            if self.stopRequested:
                break

    def addToQueue(self, job):
        self.queue.insert(0, job)

    def stop(self):
        self.stopRequested = True


def sendFile(path):
    if not device:
        print 'Device not found'
        return False
    fsender = FileSender(path)
    fsender.send(shell)


def sendCmd(cmd):
    def onCmdResponse(resultCode, data):
        print data
        global pending
        pending = False
    data = struct.pack('<HI', OP_EXEC_CMD, len(cmd))+cmd
    job = Job(data=data, response=onCmdResponse, responseRequired=True)
    shell.addToQueue(job)


def shinput(prompt=''):
    result = ''
    sys.stdout.write(prompt)
    while True:
        try:
            s = raw_input()
        except EOFError:
            stop()
        if s.strip() and s[-2] == '\\':
            result += s[:-2] + '\n'
            sys.stdout.write(' '*len(prompt))
            continue
        result += s
        return result


def init():
    global device, shell
    device = serial.Serial('COM6', baudrate=115200, timeout=0)
    shell = Shell(device)
    shell.start()

def poll():
    global pending
    while pending:
        continue
    cmd = shinput('>> ')
    
    if(cmd.strip().startswith('sendFile')):
        try:
            exec(cmd)
        except:
            print 'Unknown command:', cmd

    else:
        sendCmd(cmd)
    
    pending = True

def main():
    init()
    while True:
        poll()

def stop():
    shell.stop()
    exit()

if __name__ == "__main__":
    main()
